package IC.LIR;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import IC.BinaryOps;
import IC.DataTypes;
import IC.AST.ArrayLocation;
import IC.AST.Assignment;
import IC.AST.Break;
import IC.AST.CallStatement;
import IC.AST.Continue;
import IC.AST.ExpressionBlock;
import IC.AST.Field;
import IC.AST.Formal;
import IC.AST.ICClass;
import IC.AST.If;
import IC.AST.Length;
import IC.AST.LibraryMethod;
import IC.AST.Literal;
import IC.AST.LocalVariable;
import IC.AST.LogicalBinaryOp;
import IC.AST.LogicalUnaryOp;
import IC.AST.MathBinaryOp;
import IC.AST.MathUnaryOp;
import IC.AST.Method;
import IC.AST.NewArray;
import IC.AST.NewClass;
import IC.AST.PrimitiveType;
import IC.AST.Program;
import IC.AST.Return;
import IC.AST.Statement;
import IC.AST.StatementsBlock;
import IC.AST.StaticCall;
import IC.AST.StaticMethod;
import IC.AST.This;
import IC.AST.UnaryOp;
import IC.AST.UserType;
import IC.AST.VariableLocation;
import IC.AST.VirtualCall;
import IC.AST.VirtualMethod;
import IC.AST.While;
import IC.SemanticChecks.PropagatingVisitor;
import IC.Symbols.ClassSymbolTable;
import IC.Symbols.Kind;
import IC.Symbols.MethodSymbol;
import IC.Symbols.Symbol;
import IC.Symbols.SymbolTable;
import IC.Types.ClassType;
import IC.Types.IntType;
import IC.Types.MethodType;
import IC.Types.Type;
import IC.Types.TypeTable;

public class TranslationVisitor implements PropagatingVisitor<LirDownType, LirUpType> {

	private Map<String, String> stringLiterals;
	private Map<String, ClassLayout> classesLayouts;
	private int stringLabelIndex = 0;
	private int labelIndex = 0;
	private int testIndex = 0;
	private String FALSE_LABEL = "_false_label";
	private String END_LABEL = "_end_label";
	private String STR_LABEL = "str";
	private String TEST_LABEL = "_test_label";
	private String ALLOCATE_OBJECT = "__allocateObject";
	private String ALLOCATE_ARRAY = "__allocateArray";
	private Stack<String> endLabelStack = new Stack<String>();
	private Stack<String> falseLabelStack = new Stack<String>();
	private Stack<String> testLabelStack = new Stack<String>();


	public TranslationVisitor(){
		classesLayouts = new HashMap<String, ClassLayout>();
		stringLiterals = new HashMap<String, String>();
	}

	/**
	 * DONE
	 */
	public LirUpType visit(Program program, LirDownType d) {

		StringBuffer buffer = new StringBuffer();

		stringLiterals = new HashMap<String, String>();

		for (ICClass currentClass : program.getClasses()){
			ClassLayout currentClassLayout = null;
			if (currentClass.getName().equals("Library")){
				continue; //do not translate the library class
			}
			if (currentClass.hasSuperClass()){
				currentClassLayout = new ClassLayout(currentClass, classesLayouts.get(currentClass.getSuperClassName()));
			}
			else{
				currentClassLayout = new ClassLayout(currentClass);				
			}
			classesLayouts.put(currentClass.getName(), currentClassLayout);
		}

		LirDownType downType = new LirDownType(new LirRegister(1), 2);

		for (ICClass currentClass : program.getClasses()){
			if (currentClass.getName().equals("Library")){
				continue; //do not translate the library class
			}

			LirUpType upType = currentClass.accept(this, downType);
			if (upType == null){
				return null;
			}
			buffer.append(upType.getLirCode());
		}

		return createLirProgram(buffer);
	}

	/**
	 * DONE
	 */
	public LirUpType visit(ICClass icClass, LirDownType d) {

		StringBuffer buffer = new StringBuffer();
		buffer.append("\n#Class " + icClass.getName() + "\n");

		for (Method currentMethod : icClass.getMethods()){
			LirUpType methodUpType = currentMethod.accept(this, d);
			if (methodUpType == null){
				return null;
			}

			buffer.append(methodUpType.getLirCode());
		}

		LirUpType lirUpType = new LirUpType(buffer.toString());
		return lirUpType;
	}

	/**
	 * DONE
	 */
	public LirUpType visit(Field field, LirDownType d) {
		return new LirUpType("");
	}

	/**
	 * DONE
	 */
	public LirUpType visit(VirtualMethod method, LirDownType d) {
		return visitMethod(method, d);
	}

	/**
	 * DONE
	 */
	public LirUpType visit(StaticMethod method, LirDownType d) {
		return visitMethod(method, d);
	}

	/**
	 * DONE
	 */
	public LirUpType visit(LibraryMethod method, LirDownType d) {
		return new LirUpType("");
	}

	/**
	 * DONE
	 */
	public LirUpType visit(Formal formal, LirDownType d) {
		return new LirUpType("");
	}

	/**
	 * DONE
	 */
	public LirUpType visit(PrimitiveType type, LirDownType d) {
		return new LirUpType("");
	}

	/**
	 * DONE
	 */
	public LirUpType visit(UserType type, LirDownType d) {
		return new LirUpType("");
	}


	/**
	 * DONE
	 */
	public LirUpType visit(Assignment assignment, LirDownType d) {

		StringBuffer buffer = new StringBuffer();
		buffer.append("\n# " + assignment.toString() + "\n");
		//visit assignment (value need to be assigned)
		LirRegister lirRegisterAssign = d.getTarget();
		LirUpType upTypeAssign = assignment.getAssignment().accept(this, d);
		if (upTypeAssign == null){
			return null;
		}

		LirRegister varDestinationRegister = null;
		if (upTypeAssign.getIsRegister()){
			//because the assignment uses a register
			varDestinationRegister = new LirRegister(2);
		} else{
			//because the assignment didn't use a register, register #1 is still available
			varDestinationRegister = new LirRegister(1);
		}

		//visit the variable we need to assign to
		LirDownType downTypeVariable = new LirDownType(varDestinationRegister, varDestinationRegister.getRegNum() + 1, false);
		downTypeVariable.setLoadResToReg(false);
		LirUpType upTypeVar = assignment.getVariable().accept(this, downTypeVariable);
		if (upTypeVar == null){
			return null;
		}

		//append the generated assignment and variable lir codes
		buffer.append(upTypeAssign.getLirCode());
		buffer.append(upTypeVar.getLirCode());

		LirInstruction ins = null;
		if (assignment.getVariable() instanceof ArrayLocation){
			//variable is an array location, we know which registers the array and its index are stored in, need to MoveArray
			LirRegister arrayRegister = new LirRegister(upTypeVar.getArrayRegNum());
			String index = new LirRegister(upTypeVar.getArrayIndexRegNum()).toString();
			if (upTypeAssign.getIsRegister()){
				ins = new LirInstruction(InstructionType.MoveArray, lirRegisterAssign.toString(), arrayRegister + "[" + index + "]");
			}
			else{
				//value to assign is in memory or is an immediate. we load it straight from the memory
				ins = new LirInstruction(InstructionType.MoveArray, upTypeAssign.getImmediateOrMemory(), arrayRegister + "[" + index + "]");
			}
		}
		else{
			VariableLocation location = (VariableLocation) assignment.getVariable();
			if (location.isExternal() || (location.getEnclosingScope().lookup(location.getName()).getKind() == Kind.FIELD)){
				//variable is a field of a class, we retrieve the offset and MoveField. We already stored THIS in a register
				LirRegister externalRegister = varDestinationRegister;
				int offset = upTypeVar.getFieldOffset();
				if (upTypeAssign.getIsRegister()){
					ins = new LirInstruction(InstructionType.MoveField, lirRegisterAssign.toString(), externalRegister + "." + offset);
				}
				else{
					ins = new LirInstruction(InstructionType.MoveField, upTypeAssign.getImmediateOrMemory(), externalRegister + "." + offset);
				}

			}
			else{
				//variable is a simple variable, move the data into it
				String variableName = upTypeVar.getImmediateOrMemory();
				if (upTypeAssign.getIsRegister()){
					//use move between register and memory
					ins = new LirInstruction(InstructionType.Move, lirRegisterAssign.toString(), variableName);
				}
				else{
					//both variable and assigned value are memory of immediate, need to move one of them to register
					LirInstruction moveAssignedValueToRegIns = new LirInstruction(InstructionType.Move, upTypeAssign.getImmediateOrMemory(), lirRegisterAssign.toString());
					buffer.append(moveAssignedValueToRegIns);
					ins = new LirInstruction(InstructionType.Move, lirRegisterAssign.toString(), variableName);
				}
			}
		}
		buffer.append(ins.toString());

		return new LirUpType(buffer.toString());
	}


	/**
	 * DONE
	 */
	public LirUpType visit(CallStatement callStatement, LirDownType d) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("\n# " + callStatement.toString() + "\n");

		LirUpType upType = callStatement.getCall().accept(this, d);
		if (upType == null){
			return null;
		}

		buffer.append(upType.getLirCode());
		return new LirUpType(buffer.toString());

	}


	/**
	 * DONE
	 */
	public LirUpType visit(Return returnStatement, LirDownType d) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("\n# " + returnStatement.toString() + "\n");

		if (returnStatement.hasValue()){
			LirUpType upType = returnStatement.getValue().accept(this, d);
			if (upType == null){
				return null;
			}

			buffer.append(upType.getLirCode());

			LirInstruction ins = null;
			if (!upType.getIsRegister()){
				ins = new LirInstruction(InstructionType.Move, upType.getImmediateOrMemory(), d.getTarget().toString());
				buffer.append(ins);
			}

			ins = new LirInstruction(InstructionType.Return, d.getTarget().toString());
			buffer.append(ins);

		}
		else{
			LirInstruction ins = new LirInstruction(InstructionType.Return, new Rdummy().toString());
			buffer.append(ins);
		}
		return new LirUpType(buffer.toString());
	}

	/**
	 * DONE 
	 */
	public LirUpType visit(If ifStatement, LirDownType d) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("\n# " + ifStatement.toString() + "\n");

		String ifEndLabel = uniqueEndLabel();
		String ifFalseLabel = null;
		if (ifStatement.hasElse()){
			ifFalseLabel = uniqueFalseLabel();
			falseLabelStack.push(ifFalseLabel);
		}
		endLabelStack.push(ifEndLabel);

		LirUpType upTypeIfCondition = ifStatement.getCondition().accept(this, d);
		if (upTypeIfCondition == null){
			return null;
		}

		//add condition code
		buffer.append(upTypeIfCondition.getLirCode());

		LirInstruction ins = null;
		if (!upTypeIfCondition.getIsRegister()){
			ins = new LirInstruction(InstructionType.Move, upTypeIfCondition.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		//add compare code
		ins = new LirInstruction(InstructionType.Compare, "0", d.getTarget().toString());
		buffer.append(ins);

		//prepare the code for the if statement - will be use later for sure
		LirUpType upTypeIfOperation = ifStatement.getOperation().accept(this, d);
		if (upTypeIfOperation == null){
			return null;
		}

		//handles if-then-else cases only
		if (ifStatement.hasElse()){
			//add jump to false label
			ins = new LirInstruction(InstructionType.JumpTrue, ifFalseLabel);
			buffer.append(ins);

			//add if operation code
			buffer.append(upTypeIfOperation.getLirCode());

			//add jump to end label
			ins = new LirInstruction(InstructionType.Jump, ifEndLabel);
			buffer.append(ins);

			//add false label
			buffer.append(ifFalseLabel + ":\n");

			//add else operation code
			LirUpType upTypeElseOperation = ifStatement.getElseOperation().accept(this, d);
			if (upTypeElseOperation == null){
				return null;
			}
			buffer.append(upTypeElseOperation.getLirCode());
		} 
		//handles if-then only
		else {
			//add jump to end label
			ins = new LirInstruction(InstructionType.JumpTrue, ifEndLabel);
			buffer.append(ins);

			//add if operation code
			buffer.append(upTypeIfOperation.getLirCode());
		}

		//add end label
		buffer.append(ifEndLabel + ":\n");

		endLabelStack.pop();
		if (ifStatement.hasElse()){
			falseLabelStack.pop();
		}

		return new LirUpType(buffer.toString());
	}

	/**
	 * DONE
	 */
	public LirUpType visit(While whileStatement, LirDownType d) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("\n# " + whileStatement.toString() + "\n");

		String whileTestLabel = uniqueTestLabel();
		String whileEndLabel = uniqueEndLabel();

		testLabelStack.push(whileTestLabel);
		endLabelStack.push(whileEndLabel);

		buffer.append(whileTestLabel + ":\n");
		LirUpType upTypeCondition = whileStatement.getCondition().accept(this, d);
		if (upTypeCondition == null){
			return null;
		}

		//add condition code
		buffer.append(upTypeCondition.getLirCode());

		LirInstruction ins = null;

		if (!upTypeCondition.getIsRegister()){
			ins = new LirInstruction(InstructionType.Move, upTypeCondition.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		//add compare code
		ins = new LirInstruction(InstructionType.Compare, "0", d.getTarget().toString());
		buffer.append(ins);

		//add jump code
		ins = new LirInstruction(InstructionType.JumpTrue, whileEndLabel);
		buffer.append(ins);

		LirUpType upTypeOperation = whileStatement.getOperation().accept(this, d);
		if (upTypeOperation == null){
			return null;
		}

		//add while operation code
		buffer.append(upTypeOperation.getLirCode());

		//add jump to test label code
		ins = new LirInstruction(InstructionType.Jump, whileTestLabel);
		buffer.append(ins);

		//add end label
		buffer.append(whileEndLabel + ":\n");

		testLabelStack.pop();
		endLabelStack.pop();

		return new LirUpType(buffer.toString());
	}

	/**
	 * DONE
	 */
	public LirUpType visit(Break breakStatement, LirDownType d) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("\n# " + breakStatement.toString() + "\n");

		String currentEndLabel = endLabelStack.peek();
		LirInstruction ins = new LirInstruction(InstructionType.Jump, currentEndLabel);

		buffer.append(ins);
		return new LirUpType(buffer.toString());
	}

	/**
	 * DONE
	 */
	public LirUpType visit(Continue continueStatement, LirDownType d) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("\n# " + continueStatement.toString() + "\n");

		String currentTestLabel = testLabelStack.peek();
		LirInstruction ins = new LirInstruction(InstructionType.Jump, currentTestLabel);

		buffer.append(ins);
		return new LirUpType(buffer.toString());
	}


	/**
	 * DONE
	 */
	public LirUpType visit(StatementsBlock statementsBlock, LirDownType d) {

		StringBuffer buffer = new StringBuffer();

		for(Statement stmt: statementsBlock.getStatements()){
			LirUpType stmtLirUpType = stmt.accept(this, d);
			if (stmtLirUpType == null){
				return null;
			}
			buffer.append(stmtLirUpType.getLirCode());
		}

		return new LirUpType(buffer.toString());
	}


	public LirUpType visit(LocalVariable localVariable, LirDownType d) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("\n# " + localVariable.toString() + "\n");
		if (localVariable.hasInitValue()){
			LirUpType upType = localVariable.getInitValue().accept(this, d);
			if (upType == null){
				return null;
			}

			buffer.append(upType.getLirCode());
			if (!upType.getIsRegister()){
				LirInstruction ins = new LirInstruction(InstructionType.Move, upType.getImmediateOrMemory(), d.getTarget().toString());
				buffer.append(ins);
			}

			LirInstruction ins = new LirInstruction(InstructionType.Move, d.getTarget().toString(), localVariable.getName());
			buffer.append(ins);
		}
		return new LirUpType(buffer.toString());
	}


	
	public LirUpType visit(VariableLocation location, LirDownType d) {

 		boolean isRightOperand = d.shouldLoadResToReg();

		StringBuffer buffer = new StringBuffer();
		if (location.isExternal()){
			//evaluate external location
			LirDownType downType = new LirDownType(d.getTarget(), d.getNextRegNum());
			LirUpType externalUpType = location.getLocation().accept(this, downType);
			if (externalUpType == null){
				return null;
			}

			//create external location's code
			buffer.append(externalUpType.getLirCode());

			if (!externalUpType.getIsRegister()){
				
				//need to load a field into a register
				String variableName = externalUpType.getImmediateOrMemory();			

				//the external location was not loaded into a register, need to load it because MoveField operates on Reg.Immediate or Reg.Reg
				LirInstruction ins = new LirInstruction(InstructionType.Move, variableName, d.getTarget().toString());
				buffer.append(ins);
			}
			
			Type locationType = location.getLocation().getNodeType();
			String className = ((ClassType) locationType).getName();
			int fieldOffset = classesLayouts.get(className).getFieldOffset(location.getName());

			//check null
			LirInstruction checkNullIns = new LirInstruction(InstructionType.StaticCall, "__checkNullRef(a=" + d.getTarget().toString() + ")",new Rdummy().toString());
			buffer.append(checkNullIns);

			//create load field code
			if (isRightOperand){
				//the field is used as assigned value, need to load it into a register
				LirInstruction moveFieldIns = new LirInstruction(InstructionType.MoveField, d.getTarget() + "." + fieldOffset, d.getTarget().toString());
				buffer.append(moveFieldIns);
			}

			return new LirUpType(buffer.toString(), fieldOffset);
		}
		else {
			//variable is not external
			Symbol symbol = location.getEnclosingScope().lookup(location.getName());
			if (symbol.getKind() == Kind.FIELD){
				//variable is a field of this, need to get this class name
				SymbolTable symbolTable = location.getEnclosingScope();
				while (!(symbolTable instanceof ClassSymbolTable)){
					symbolTable = symbolTable.getParent();
				}
				ClassSymbolTable cst = (ClassSymbolTable) symbolTable;
				String className = cst.getName();
				//retrieve field offset in this class
				int fieldOffset = classesLayouts.get(className).getFieldOffset(location.getName());

				//move this into a register for both loading and storing cases
				LirInstruction moveIns = new LirInstruction(InstructionType.Move, "this", d.getTarget().toString());
				buffer.append(moveIns);

				if (isRightOperand){
					//if need to load the field into a register, load it
					LirInstruction moveFieldIns = new LirInstruction(InstructionType.MoveField, d.getTarget() + "." + fieldOffset, d.getTarget().toString());
					buffer.append(moveFieldIns.toString());
				}

				//return the lir code + field offset (in case we need to store in this field a value by an assignment statement)
				return new LirUpType(buffer.toString(), fieldOffset);
			}
			else{
				//return the variable name anyway, so external variables or assignment statements could use this variable name
				return new LirUpType(buffer.toString(), location.getName(), false);
			}
		}

	}


	/**
	 * DONE
	 */
	public LirUpType visit(ArrayLocation location, LirDownType d) {

		StringBuffer buffer = new StringBuffer();

		int arrayLabel = location.getLabel();
		int indexLabel = location.getLabel();
		boolean isRightOperand = d.shouldLoadResToReg();

		LirUpType arrayUpType = null;
		LirUpType indexUpType = null;
		LirRegister nextRegister = new LirRegister(d.getNextRegNum());

		if (arrayLabel >= indexLabel || indexLabel == Integer.MAX_VALUE){
			//keep original code generation 
			LirDownType arrayDownType = new LirDownType(d.getTarget(), d.getNextRegNum(), true);
			arrayUpType = location.getArray().accept(this, arrayDownType);
			if (arrayUpType == null){
				return null;
			}

			indexUpType = location.getIndex().accept(this, new LirDownType(nextRegister, d.getNextRegNum() + 1, true));
			if (indexUpType == null){
				return null;
			}
			buffer.append(arrayUpType.getLirCode());
			buffer.append(indexUpType.getLirCode());
		}
		else {
			//switch the order or generation
			indexUpType = location.getIndex().accept(this, new LirDownType(nextRegister, d.getNextRegNum() + 1, true));
			if (indexUpType == null){
				return null;
			}

			arrayUpType = location.getArray().accept(this, new LirDownType(d.getTarget(), d.getNextRegNum() + 1, true));
			if (arrayUpType == null){
				return null;
			}
			buffer.append(indexUpType.getLirCode());
			buffer.append(arrayUpType.getLirCode());
		}

		//load the array into a register if needed
		if (!arrayUpType.getIsRegister()){
			LirInstruction ins = new LirInstruction(InstructionType.Move, arrayUpType.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		//check if array is null
		LirInstruction checkNullIns = new LirInstruction(InstructionType.StaticCall, "__checkNullRef(a=" + d.getTarget().toString() + ")",new Rdummy().toString());
		buffer.append(checkNullIns);	

		//load the index into a register if needed
		if (!indexUpType.getIsRegister()){
			LirInstruction moveIns = new LirInstruction(InstructionType.Move, indexUpType.getImmediateOrMemory(), nextRegister.toString());
			buffer.append(moveIns);
		}

		//check array access
		LirInstruction checkArrayAccessIns= new LirInstruction(InstructionType.StaticCall, "__checkArrayAccess(a=" + d.getTarget().toString() + ",i=" + nextRegister.toString() + ")",new Rdummy().toString());
		buffer.append(checkArrayAccessIns);	

		if (isRightOperand){
			//Used as a value of an assignment so we need to load the value into a register
			LirInstruction ins = new LirInstruction(InstructionType.MoveArray, d.getTarget() + "[" + nextRegister + "]", d.getTarget().toString());
			buffer.append(ins);
			return new LirUpType(buffer.toString(), true);
		}

		//we didn't load the array location into a register yet
		return new LirUpType(buffer.toString(), d.getTarget().getRegNum(), nextRegister.getRegNum(), false);
	}


	/**
	 * DONE
	 */
	public LirUpType visit(StaticCall call, LirDownType d) {

		int numberOfArguments = call.getArguments().size();
		LirDownType downType = d;
		StringBuffer buffer = new StringBuffer();

		String [] lirRegisters = new String[numberOfArguments]; 
		//evaluate all parameters
		for (int i = 0; i< numberOfArguments; i++){
			LirUpType argLirUpType = null;
			if ((argLirUpType = call.getArguments().get(i).accept(this,downType)) == null){
				return null;
			}
			buffer.append(argLirUpType.getLirCode().toString());
			if (!argLirUpType.getIsRegister()){
				LirInstruction ins = new LirInstruction(InstructionType.Move, argLirUpType.getImmediateOrMemory(), downType.getTarget().toString());
				buffer.append(ins);
			}
			lirRegisters[i] = downType.getTarget().toString();

			LirRegister nexLirRegister = new LirRegister(downType.getNextRegNum());
			downType = new LirDownType(nexLirRegister, downType.getNextRegNum()+1);			
		}

		LirInstruction methodCallIns;

		if (call.getClassName().equals("Library")){
			StringBuffer registers = new StringBuffer();
			for (int i = 0; i < numberOfArguments; i++){
				String endOfLine = (i == (numberOfArguments - 1) ? "" : ",");
				registers.append(lirRegisters[i] + endOfLine);
			} 
			methodCallIns = new LirInstruction(InstructionType.Library, "__" + call.getName() + "(" + registers + ")", d.getTarget().toString());

		} else {
			Method method = classesLayouts.get(call.getClassName()).getMethodAst(call.getName());
			List<Formal> formals = method.getFormals();
			StringBuffer formalsAndRegBuffer = new StringBuffer();
			for (int i = 0; i < numberOfArguments; i++){
				String endOfLine = (i == (numberOfArguments - 1) ? "" : ",");
				formalsAndRegBuffer.append(formals.get(i).getName() + "=" + lirRegisters[i] + endOfLine);
			}
			String methodLabel = classesLayouts.get(call.getClassName()).getMethodLabel(call.getName());
			methodCallIns = new LirInstruction(InstructionType.StaticCall, methodLabel+"(" + formalsAndRegBuffer + ")", d.getTarget().toString());
		}

		buffer.append(methodCallIns.toString());

		return new LirUpType(buffer.toString());
	}


	public LirUpType visit(VirtualCall call, LirDownType d) {
		ClassLayout classLayout;

		LirUpType objectUpType = null;
		StringBuffer buffer = new StringBuffer();

		if (call.isExternal()){
			classLayout = classesLayouts.get(call.getLocation().getNodeType().toString());
			if ((objectUpType = call.getLocation().accept(this, d)) == null){
				return null;
			}
			buffer.append(objectUpType.getLirCode());

			//make sure we have the object in the target register
			if (!objectUpType.getIsRegister()){
				LirInstruction ins = new LirInstruction(InstructionType.Move, objectUpType.getImmediateOrMemory(), d.getTarget().toString());
				buffer.append(ins);
			}

		} else {
			//get the current class that "this" refers to
			SymbolTable table = call.getEnclosingScope();
			while (!(table instanceof ClassSymbolTable)){
				table = table.getParent();
			}
			classLayout = classesLayouts.get(table.getName());

			//move "this" into a register
			LirInstruction ins = new LirInstruction(InstructionType.Move, "this", d.getTarget().toString());
			buffer.append(ins);
		}

		//check null at this point so we won't evaluate the arguments in vein
		LirInstruction checkNullIns = new LirInstruction(InstructionType.StaticCall, "__checkNullRef(a=" + d.getTarget().toString() + ")",new Rdummy().toString());
		buffer.append(checkNullIns);

		int numberOfArguments = call.getArguments().size();
		LirDownType downType = new LirDownType(new LirRegister(d.getNextRegNum()), d.getNextRegNum() + 1);

		String [] lirRegisters = new String[numberOfArguments]; 

		//evaluate all parameters
		for (int i = 0; i< numberOfArguments; i++){
			LirUpType argLirUpType = null;
			if ((argLirUpType = call.getArguments().get(i).accept(this,downType)) == null){
				return null;
			}
			buffer.append(argLirUpType.getLirCode().toString());
			if (!argLirUpType.getIsRegister()){
				LirInstruction ins = new LirInstruction(InstructionType.Move, argLirUpType.getImmediateOrMemory(), downType.getTarget().toString());
				buffer.append(ins);
			}
			lirRegisters[i] = downType.getTarget().toString();

			LirRegister nexLirRegister = new LirRegister(downType.getNextRegNum());
			downType = new LirDownType(nexLirRegister, downType.getNextRegNum()+1);			
		}

		//get the method formals
		Method method = classLayout.getMethodAst(call.getName());

		List<Formal> formals = method.getFormals();

		StringBuffer formalsAndRegBuffer = new StringBuffer();
		for (int i = 0; i < numberOfArguments; i++){
			String endOfLine = (i == (numberOfArguments - 1) ? "" : ",");
			formalsAndRegBuffer.append(formals.get(i).getName() + "=" + lirRegisters[i] + endOfLine);
		}

		int methodOffset = classLayout.getMethodOffset(call.getName());

		//add the call command
		LirInstruction virtualCallIns = new LirInstruction(InstructionType.VirtualCall, d.getTarget() + "." + methodOffset + "(" + formalsAndRegBuffer + ")", d.getTarget().toString());
		buffer.append(virtualCallIns.toString());
		return new LirUpType(buffer.toString());
	}

	/**
	 * DONE - do not load "this" into a register by default (same as literals)
	 */
	public LirUpType visit(This thisExpression, LirDownType d) {
		LirUpType lirUpType = new LirUpType("", "this", false);
		return lirUpType;
	}


	/**
	 * DONE
	 */
	public LirUpType visit(NewClass newClass, LirDownType d) {
		ClassLayout classLayout = classesLayouts.get(newClass.getName());
		int sizeOfObject = classLayout.getNumOfBytesPerObject();

		StringBuffer buffer = new StringBuffer();

		LirInstruction ins = new LirInstruction(InstructionType.Library, allocateObject(sizeOfObject), d.getTarget().toString());
		buffer.append(ins.toString());

		ins = new LirInstruction(InstructionType.MoveField, classLayout.getDispatchVectorNameOnly() ,d.getTarget().toString() + ".0");
		buffer.append(ins.toString());

		LirUpType lirUpType = new LirUpType(buffer.toString());
		return lirUpType;
	}


	/**
	 * DONE
	 */
	public LirUpType visit(NewArray newArray, LirDownType d) {

		LirUpType sizeUpType = newArray.getSize().accept(this, d);
		if (sizeUpType == null){
			return null;
		}

		StringBuffer buffer = new StringBuffer();
		buffer.append(sizeUpType.getLirCode());

		LirInstruction ins = null;

		if (!sizeUpType.getIsRegister()){
			//making sure we have the array in the target register so we could check first for null reference
			ins = new LirInstruction(InstructionType.Move, sizeUpType.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		String argsAndReg = "n=" + d.getTarget().toString();
		ins = new LirInstruction(InstructionType.StaticCall, "__checkSize(" + argsAndReg + ")", new Rdummy().toString());
		buffer.append(ins);

		//add multiply of size * 4 code
		ins = new LirInstruction(InstructionType.Mul, "4", d.getTarget().toString());
		buffer.append(ins);

		//add array allocation code
		ins = new LirInstruction(InstructionType.Library, ALLOCATE_ARRAY + "(" + d.getTarget() + ")", d.getTarget().toString());
		buffer.append(ins);		

		return new LirUpType(buffer.toString());
	}


	/**
	 * DONE
	 */
	public LirUpType visit(Length length, LirDownType d) {


		LirUpType arrayUpType = length.getArray().accept(this, d);
		if (arrayUpType == null){
			return null;
		}

		StringBuffer buffer = new StringBuffer();
		buffer.append(arrayUpType.getLirCode());

		LirInstruction ins = null;

		//making sure we have the array in the target register so we could check first for null reference
		if (!arrayUpType.getIsRegister()){
			ins = new LirInstruction(InstructionType.Move, arrayUpType.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		String argsAndReg = "a=" + d.getTarget().toString();
		ins = new LirInstruction(InstructionType.StaticCall, "__checkNullRef(" + argsAndReg + ")", new Rdummy().toString());
		buffer.append(ins);

		ins = new LirInstruction(InstructionType.ArrayLength, d.getTarget().toString(), d.getTarget().toString());
		buffer.append(ins);
		return new LirUpType(buffer.toString());
	}

	/**
	 * DONE
	 */
	public LirUpType visit(MathBinaryOp binaryOp, LirDownType d) {

		int leftOperandLabel = binaryOp.getFirstOperand().getLabel();
		int rightOperandLabel = binaryOp.getSecondOperand().getLabel();

		StringBuffer buffer = new StringBuffer();

		LirUpType lirUpTypeLeft = null;
		LirUpType lirUpTypeRight = null;
		LirRegister nextRegister = new LirRegister(d.getNextRegNum());
		
		//if secondOperandLabel == Integer.MAX_VALUE it might involve a method call, which is not side-effect free
		if (leftOperandLabel >= rightOperandLabel || rightOperandLabel == Integer.MAX_VALUE){
			//generate code in the regular order
			LirDownType downTypeLeft = new LirDownType(d.getTarget(), d.getNextRegNum() + 1);
			lirUpTypeLeft = binaryOp.getFirstOperand().accept(this, downTypeLeft);
			if (lirUpTypeLeft == null){
				return null;
			}

			buffer.append(lirUpTypeLeft.getLirCode());

			LirDownType downTypeRight = new LirDownType(nextRegister, d.getNextRegNum() + 1);
			downTypeRight.setLoadResToReg(true);
			lirUpTypeRight = binaryOp.getSecondOperand().accept(this, downTypeRight);
			if (lirUpTypeRight == null){
				return null;
			}

			buffer.append(lirUpTypeRight.getLirCode());
		}
		else{
			//switch order of code generation
			LirDownType downTypeRight = new LirDownType(nextRegister, d.getNextRegNum() + 1);
			downTypeRight.setLoadResToReg(true);
			lirUpTypeRight = binaryOp.getSecondOperand().accept(this, new LirDownType(nextRegister, d.getNextRegNum() + 1));
			if (lirUpTypeRight == null){
				return null;
			}

			buffer.append(lirUpTypeRight.getLirCode());

			LirDownType downTypeLeft = new LirDownType(d.getTarget(), d.getNextRegNum() + 1);
			downTypeLeft.setLoadResToReg(true);
			lirUpTypeLeft = binaryOp.getFirstOperand().accept(this, downTypeLeft);
			if (lirUpTypeLeft == null){
				return null;
			}

			buffer.append(lirUpTypeLeft.getLirCode());
		}

		if (!lirUpTypeLeft.getIsRegister()){
			LirInstruction ins = new LirInstruction(InstructionType.Move, lirUpTypeLeft.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		InstructionType instType = null;
		boolean needStrCat = false;

		switch (binaryOp.getOperator()){
		case PLUS:
			//we can check only the first operand sicnce semantic check already validated this
			if (binaryOp.getFirstOperand().getNodeType() instanceof IntType)
				instType = InstructionType.Add;
			else 
				needStrCat = true;
			break;
		case MINUS:
			instType = InstructionType.Sub;
			break;
		case MULTIPLY:
			instType = InstructionType.Mul;
			break;
		case DIVIDE:
			instType = InstructionType.Div;
			break;
		case MOD:
			instType = InstructionType.Mod;
			break;
		default:
			return null;
		}

		if (lirUpTypeRight.getIsRegister()){

			LirRegister rightOperandResultRegister = new LirRegister(d.getNextRegNum());
			if (instType == InstructionType.Div){
				//check zero
				LirInstruction checkZeroIns = new LirInstruction(InstructionType.StaticCall, "__checkZero(b=" + rightOperandResultRegister + ")",new Rdummy().toString());
				buffer.append(checkZeroIns);
			}

			//perform the operation
			LirInstruction operationIns;
			if (needStrCat) {
				String registers = d.getTarget().toString() + "," + rightOperandResultRegister.toString();
				operationIns = new LirInstruction(InstructionType.Library, "__stringCat(" + registers +")", d.getTarget().toString());
			} else {
				operationIns = new LirInstruction(instType, rightOperandResultRegister.toString(), d.getTarget().toString());
			}
			buffer.append(operationIns);
		}
		else{
			LirRegister rightOperandResultRegister = null;
			//in case we need to move the right operand to register (whether due to runtime check or strCat)
			if (instType == InstructionType.Div || needStrCat){
				rightOperandResultRegister = new LirRegister(d.getNextRegNum());
				//move the value into a register
				LirInstruction moveIns = new LirInstruction(InstructionType.Move, lirUpTypeRight.getImmediateOrMemory(), rightOperandResultRegister.toString());
				buffer.append(moveIns);
			}

			if (instType == InstructionType.Div){
				//check zero
				LirInstruction checkZeroIns = new LirInstruction(InstructionType.StaticCall, "__checkZero(b=" + rightOperandResultRegister + ")",new Rdummy().toString());
				buffer.append(checkZeroIns);

				//perform the operation
				LirInstruction operationIns = new LirInstruction(instType, rightOperandResultRegister.toString(), d.getTarget().toString());
				buffer.append(operationIns);
			} else{
				LirInstruction operationIns;
				if (needStrCat) {
					String registers = d.getTarget().toString() + "," +  rightOperandResultRegister.toString();
					operationIns = new LirInstruction(InstructionType.Library, "__stringCat(" + registers +")", d.getTarget().toString());
				} else {
					operationIns = new LirInstruction(instType, lirUpTypeRight.getImmediateOrMemory(), d.getTarget().toString());
				}
				buffer.append(operationIns); 

			}
		}

		LirUpType lirUpType = new LirUpType(buffer.toString());
		return lirUpType;
	}

	/**
	 * DONE
	 */
	public LirUpType visit(LogicalBinaryOp binaryOp, LirDownType d) {

		switch(binaryOp.getOperator()){
		case LAND:
		case LOR:
			return visitAndOr(binaryOp, d);
		case LT:
			return visitComparativeBinaryOps(binaryOp, d, InstructionType.JumpL);
		case LTE:
			return visitComparativeBinaryOps(binaryOp, d, InstructionType.JumpLE);
		case GT:
			return visitComparativeBinaryOps(binaryOp, d, InstructionType.JumpG);
		case GTE:
			return visitComparativeBinaryOps(binaryOp, d, InstructionType.JumpGE);
		case EQUAL:
			return visitComparativeBinaryOps(binaryOp, d, InstructionType.JumpTrue);
		case NEQUAL:
			return visitComparativeBinaryOps(binaryOp, d, InstructionType.JumpFalse);
		default:
			return null;
		}
	}

	/**
	 * DONE
	 */
	public LirUpType visit(MathUnaryOp unaryOp, LirDownType d) {
		return visitUnaryOp(unaryOp, d, InstructionType.Neg);
	}


	/**
	 * DONE
	 */
	public LirUpType visit(LogicalUnaryOp unaryOp, LirDownType d) {
		return visitUnaryOp(unaryOp, d, InstructionType.Not);
	}

	/**
	 * DONE - do not load literals into memory by default
	 */
	public LirUpType visit(Literal literal, LirDownType d) {
		String literalRepresentation = null;

		switch(literal.getType()){
		case STRING:
			if (stringLiterals.get((String) literal.getValue()) == null){
				stringLiterals.put(((String) literal.getValue()), uniqueStrLabel());
			}
			literalRepresentation = stringLiterals.get((String) literal.getValue());
			break;
		case INTEGER:
			literalRepresentation =literal.getValue().toString();
			break;
		case TRUE:
			literalRepresentation = "1";
			break;
		case FALSE:
		case NULL:
			literalRepresentation = "0";
			break;
		}
		LirUpType lirUpType = new LirUpType("", literalRepresentation, false);
		return lirUpType;
	}

	/**
	 * DONE
	 */
	public LirUpType visit(ExpressionBlock expressionBlock, LirDownType d) {

		return expressionBlock.getExpression().accept(this, d);
	}

	private LirUpType createLirProgram(StringBuffer buffer){
		StringBuffer finalString = new StringBuffer();

		finalString.append("# String Literals Labels\n");
		addRuntimeErrosStrings(finalString);

		Set<String> stringLabelsKeys = stringLiterals.keySet();
		for (String stringLabel : stringLabelsKeys){
			finalString.append(stringLiterals.get(stringLabel) + ": \"" +  stringLabel + "\"\n");
		}
		
		StringBuffer dispatchVectorBuffer = new StringBuffer();
		dispatchVectorBuffer.append("\n# Classes Dispatch Vectors\n\n");
		StringBuffer fieldsOffsetsBuffer = new StringBuffer();
		fieldsOffsetsBuffer.append("\n# Classes Fields Offsets\n\n");

		Set<String> classesLayoutKeys = classesLayouts.keySet();
		for (String classLayoutKey : classesLayoutKeys){
			dispatchVectorBuffer.append(classesLayouts.get(classLayoutKey).getDispatchVector());
			createFieldsOffsets(classesLayouts.get(classLayoutKey).getFieldsOffsets(), classLayoutKey, fieldsOffsetsBuffer);
		}
		finalString.append(dispatchVectorBuffer.toString());
		finalString.append(fieldsOffsetsBuffer.toString());

		finalString.append("\n# Methods\n");
		createRuntimeChecks(finalString);
		finalString.append(buffer.toString());

		//add the exit label, used by runtime errors checks
		finalString.append("_exit:\n");

		return new LirUpType(finalString.toString());
	}
	
	private void createFieldsOffsets(Map<String, Integer> offsets, String className, StringBuffer sb){
		sb.append("## " + className + "\n");
		for (String fieldName : offsets.keySet()){
			sb.append(String.format("#Field: %s\t #Offset: %d\n", fieldName, offsets.get(fieldName)));
		}
		sb.append("\n");
	}

	private void addRuntimeErrosStrings(StringBuffer buffer){

		buffer.append("str_null_ref: \"Runtime Error: Null pointer dereference!\"\n");
		buffer.append("str_array_access: \"Runtime Error: Array index out of bounds!\"\n");
		buffer.append("str_size: \"Runtime Error: Array allocation with negative array size!\"\n");
		buffer.append("str_zero: \"Runtime Error: Division by zero!\"\n");
	}

	private void createRuntimeChecks(StringBuffer buffer){

		String runtimeChecksString = "\n# Runtime checks:\n" +
				//generate code for __checkNullRef
				"__checkNullRef:\n" +
				"Move a,Rc1\n" + //need to use separate registers for this action
				"Compare 0,Rc1\n" +
				"JumpTrue __checkNullRef_err\n" +
				"Return 1\n" +
				"__checkNullRef_err:\n" +
				"Library __println(str_null_ref),Rdummy\n" +
				"Jump _exit\n\n" +

		//generate code for __checkArrayAccess
		"__checkArrayAccess:\n" +
		"Move a,Rc1\n" +
		"Move i,Rc2\n" +
		"ArrayLength Rc1,Rc1\n" +
		"Compare Rc1,Rc2\n" +
		"JumpGE __checkArrayAccess_err\n" +
		"Compare 0,Rc2\n" +
		"JumpL __checkArrayAccess_err\n" +
		"Return 1\n" +
		"__checkArrayAccess_err:\n" +
		"Library __println(str_array_access),Rdummy\n" +
		"Jump _exit\n\n" +

		//generate code for __checkSize
		"__checkSize:\n" +
		"Move n,Rc1\n" +
		"Compare 0,Rc1\n" +
		"JumpL __checkSize_err\n" +
		"Return 1\n" +
		"__checkSize_err:\n" +
		"Library __println(str_size),Rdummy\n" +
		"Jump _exit\n\n" +

		//generate code for __checkZero
		"__checkZero:\n" +
		"Move b,Rc1\n" +
		"Compare 0,Rc1\n" +
		"JumpTrue __checkZero_err\n" +
		"Return 1\n" +
		"__checkZero_err:\n" +
		"Library __println(str_zero),Rdummy\n" +
		"Jump _exit\n\n";

		buffer.append(runtimeChecksString);
	}

	private LirUpType visitMethod(Method method, LirDownType d){

		boolean isMain = isMainMethod(method);
		String parentClassName = method.getEnclosingScope().getName();
		String methodLabel = classesLayouts.get(parentClassName).getMethodLabel(method.getName());

		StringBuffer buffer = new StringBuffer();
		if (isMain){
			buffer.append("\n_ic_main:");
		}else{
			buffer.append("\n" + methodLabel + ":");
		}

		for (Statement currentStmt : method.getStatements()){
			LirUpType stmtLirUpType = currentStmt.accept(this,d);
			if (stmtLirUpType == null){
				return null;
			}
			buffer.append(stmtLirUpType.getLirCode());
		}

		if (isMain){
			LirInstruction ins = new LirInstruction(InstructionType.Library, "__exit(0)", new Rdummy().toString());
			buffer.append(ins);			
		}
		else if (method.getType() instanceof PrimitiveType){
			PrimitiveType methodReturnType = (PrimitiveType) method.getType();
			if (methodReturnType.getType() == DataTypes.VOID){
				LirInstruction ins = new LirInstruction(InstructionType.Return, new Rdummy().toString());
				buffer.append("\n" + ins);
			}
		}
		return new LirUpType(buffer.toString());
	}

	private boolean isMainMethod(Method method) {

		if (!method.getName().toLowerCase().equals("main")){
			return false;
		}
		Symbol mainSymbol = method.getEnclosingScope().lookup(method.getName(), false);
		if ((mainSymbol != null && mainSymbol instanceof MethodSymbol) && (((MethodSymbol) mainSymbol).isStatic())){
			MethodSymbol mainMethodSymbol = (MethodSymbol) mainSymbol;
			MethodType type = (MethodType) mainMethodSymbol.getType();
			if (type.getReturnType() == TypeTable.getVoidType()){
				Type[] paramTypes = type.getParamTypes();
				if (paramTypes.length == 1){
					if (paramTypes[0] == TypeTable.arrayType(TypeTable.getStringType())){
						return true;
					}
				}
			}
		}		

		return false;
	}

	private LirUpType visitUnaryOp(UnaryOp op, LirDownType d, InstructionType intstuctionType){

		LirUpType lirUpType = op.getOperand().accept(this, d);
		if (lirUpType == null){
			return null;
		}

		StringBuffer buffer = new StringBuffer();
		buffer.append(lirUpType.getLirCode());

		if (!lirUpType.getIsRegister()){
			//if the value was not stored in register, load it into a one.
			LirInstruction ins = new LirInstruction(InstructionType.Move, lirUpType.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		LirInstruction ins = new LirInstruction(intstuctionType, d.getTarget().toString());
		buffer.append(ins.toString());
		return new LirUpType(buffer.toString());	
	}

	private LirUpType visitAndOr(LogicalBinaryOp binaryOp, LirDownType d){
		//evaluate left operand
		LirUpType lirUpTypeLeft = binaryOp.getFirstOperand().accept(this, d);
		if (lirUpTypeLeft == null){
			return null;
		}

		//create left operand code
		StringBuffer buffer = new StringBuffer();
		buffer.append(lirUpTypeLeft.getLirCode().toString());

		if (!lirUpTypeLeft.getIsRegister()){
			LirInstruction ins = new LirInstruction(InstructionType.Move, lirUpTypeLeft.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		//create compare code
		String compareToValue = binaryOp.getOperator() == BinaryOps.LAND ? "0" : "1";
		LirInstruction ins = new LirInstruction(InstructionType.Compare,compareToValue,d.getTarget().toString());
		buffer.append(ins.toString());

		//create jumpTrue code
		String endLabel = uniqueEndLabel();
		ins = new LirInstruction(InstructionType.JumpTrue,endLabel);
		buffer.append(ins.toString());

		//evaluate right operand
		LirRegister nextRegister = new LirRegister(d.getNextRegNum());	
		LirUpType lirUpTypeRight = binaryOp.getSecondOperand().accept(this, new LirDownType(nextRegister, d.getNextRegNum()+1));
		if (lirUpTypeRight == null){
			return null;
		}

		//create right operand code
		buffer.append(lirUpTypeRight.getLirCode().toString());

		if (!lirUpTypeRight.getIsRegister()){
			ins = new LirInstruction(InstructionType.Move, lirUpTypeRight.getImmediateOrMemory(), nextRegister.toString());
			buffer.append(ins);
		}

		//create binaryOp code (and/or)
		InstructionType insType = binaryOp.getOperator() == BinaryOps.LAND ? InstructionType.And : InstructionType.Or;		
		ins = new LirInstruction(insType,nextRegister.toString(),d.getTarget().toString());
		buffer.append(ins.toString());

		//create endLabel code
		ins = new LirInstruction(endLabel);
		buffer.append(ins.toString());

		LirUpType lirUpType = new LirUpType(buffer.toString());
		return lirUpType;

	}

	private String uniqueEndLabel(){
		String uniqueLabel = END_LABEL + labelIndex;
		labelIndex++;
		return uniqueLabel;
	}

	private String uniqueFalseLabel(){
		String uniqueLabel = FALSE_LABEL + labelIndex;
		labelIndex++;
		return uniqueLabel;
	}

	private String uniqueStrLabel(){
		String uniqueLabel = STR_LABEL + stringLabelIndex;
		stringLabelIndex++;
		return uniqueLabel;
	}

	private String uniqueTestLabel(){
		String uniqueLabel = TEST_LABEL + testIndex;
		testIndex++;
		return uniqueLabel;
	}


	private LirUpType visitComparativeBinaryOps(LogicalBinaryOp binaryOp, LirDownType d, InstructionType insType){
		StringBuffer buffer = new StringBuffer();

		int leftOperandLabel = binaryOp.getFirstOperand().getLabel();
		int rightOperandLabel = binaryOp.getSecondOperand().getLabel();

		LirUpType lirUpTypeLeft = null;
		LirUpType lirUpTypeRight = null;
		LirRegister nextRegister = new LirRegister(d.getNextRegNum());	

		//if leftOrightOperandLabelperandLabel == Integer.MAX_VALUE it might involve a method call, which is not side-effect free
		if (leftOperandLabel >= rightOperandLabel || rightOperandLabel == Integer.MAX_VALUE){
			//generate code in the regular order

			//generate code for left operand
			LirDownType downTypeLeft = new LirDownType(d.getTarget(), d.getNextRegNum() + 1);
			lirUpTypeLeft = binaryOp.getFirstOperand().accept(this, downTypeLeft);
			if (lirUpTypeLeft == null){
				return null;
			}
			buffer.append(lirUpTypeLeft.getLirCode().toString());

			//generate code for right operand
			LirDownType downTypeRight = new LirDownType(nextRegister, d.getNextRegNum() + 1);
			lirUpTypeRight = binaryOp.getSecondOperand().accept(this, downTypeRight);
			if (lirUpTypeRight == null){
				return null;
			}

			buffer.append(lirUpTypeRight.getLirCode().toString());
		}
		else{
			//switch code generation order

			//generate code for right operand
			LirDownType downTypeRight = new LirDownType(nextRegister, d.getNextRegNum() + 1);
			lirUpTypeRight = binaryOp.getSecondOperand().accept(this, downTypeRight);
			if (lirUpTypeRight == null){
				return null;
			}
			buffer.append(lirUpTypeRight.getLirCode());

			//generate code for left operand
			LirDownType downTypeLeft = new LirDownType(d.getTarget(), d.getNextRegNum() + 1);
			lirUpTypeLeft = binaryOp.getFirstOperand().accept(this, downTypeLeft);
			if (lirUpTypeLeft == null){
				return null;
			}

			buffer.append(lirUpTypeLeft.getLirCode());
		}

		if (!lirUpTypeLeft.getIsRegister()){
			LirInstruction ins = new LirInstruction(InstructionType.Move, lirUpTypeLeft.getImmediateOrMemory(), d.getTarget().toString());
			buffer.append(ins);
		}

		//we assume that the evaluation is false
		LirRegister tempRegister = new LirRegister(d.getNextRegNum() + 1);	
		LirInstruction ins = new LirInstruction(InstructionType.Move, "1", tempRegister.toString());
		buffer.append(ins);

		//create compare
		if (lirUpTypeRight.getIsRegister()){
			ins = new LirInstruction(InstructionType.Compare, nextRegister.toString(), d.getTarget().toString());
		}
		else{
			ins = new LirInstruction(InstructionType.Compare,  lirUpTypeRight.getImmediateOrMemory(), d.getTarget().toString());
		}
		buffer.append(ins);	

		//create jump code
		String endLable = uniqueEndLabel();
		ins = new LirInstruction(insType, endLable);
		buffer.append(ins);	

		//evaluation is true	
		ins = new LirInstruction(InstructionType.Move, "0", tempRegister.toString());
		buffer.append(ins);

		//create end label code
		ins = new LirInstruction(endLable);
		buffer.append(ins);

		//move result to target register	
		ins = new LirInstruction(InstructionType.Move, tempRegister.toString(), d.getTarget().toString());
		buffer.append(ins);

		LirUpType lirUpType = new LirUpType(buffer.toString());
		return lirUpType;
	}

	private String allocateObject(int size){
		return ALLOCATE_OBJECT + "(" + size + ")";
	}

}
